package cn.demoncat.util.lang;

import cn.demoncat.util.exception.BizRuntimeException;
import cn.demoncat.util.exception.ParamRuntimeException;
import cn.demoncat.util.lang.constant.JDBCConstant;
import cn.demoncat.util.lang.constant.StringConstant;
import cn.demoncat.util.lang.fn.ConsumerThrow;
import cn.demoncat.util.lang.fn.FunctionThrow;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.sql.*;
import java.util.ArrayList;
import java.util.List;

/**
 * 数据库JDBC工具
 * 
 * @author 延晓磊
 *
 * @since 2018年2月12日
 */
public class JDBCUtil {

	private static final Logger LOG = LoggerFactory.getLogger(JDBCUtil.class);
	
	// 异常
	private static final RuntimeException EXP_TYPE = new ParamRuntimeException("数据库类型为：1 Mysql, 2 Mariadb, 3 Oracle");
	private static final RuntimeException EXP_TYPE_M = new ParamRuntimeException("数据库类型为：1 Mysql, 2 Mariadb");
	private static final String CONN_ERROR = "数据库连接失败";

	/**
	 * 获取数据库类型
	 *
	 * @param url	数据库连接
	 * @return
	 *
	 * @author 延晓磊
	 *
	 * @since 2018年2月12日
	 */
	public static int getType(String url) {
		if (url.startsWith("jdbc:mysql")) {
			return 1;
		}else if (url.startsWith("jdbc:mariadb")) {
			return 2;
		}else{
			return 3;
		}
	}

	/**
	 * 获取数据库驱动
	 * 
	 * @param type	数据库类型：1 Mysql, 2 Mariadb, 3 Oracle
	 * @return
	 * 
	 * @author 延晓磊
	 *
	 * @since 2018年2月12日
	 */
	public static String getDriver(int type) {
		switch (type) {
		case 1:
			return JDBCConstant.MYSQL_DRIVER;
		case 2:
			return JDBCConstant.MARIADB_DRIVER;
		case 3:
			return JDBCConstant.ORACLE_DRIVER;
		default:
			throw EXP_TYPE;
		}
	}

	/**
	 * 获取数据库驱动
	 *
	 * @param url	数据库连接
	 * @return
	 *
	 * @author 延晓磊
	 *
	 * @since 2018年2月12日
	 */
	public static String getDriver(String url) {
		return getDriver(getType(url));
	}

	/**
	 * 获取数据库连接的URL
	 *
	 * @param type			数据库类型：1 Mysql, 2 Mariadb, 3 Oracle
	 * @param host 			数据库IP
	 * @param port			数据库端口
	 * @param dbName		数据库名
	 * @return
	 *
	 * @author 延晓磊
	 *
	 * @since 2018年2月12日
	 */
	public static String getUrl(int type, String host, int port, String dbName) {
		return getUrl(type, host + ":" + port, dbName);
	}

	/**
	 * 获取数据库连接的URL
	 * 
	 * @param type			数据库类型：1 Mysql, 2 Mariadb, 3 Oracle
	 * @param hostAndPort	数据库地址：ip:port
	 * @param dbName		数据库名
	 * @return
	 * 
	 * @author 延晓磊
	 *
	 * @since 2018年2月12日
	 */
	public static String getUrl(int type, String hostAndPort, String dbName) {
		if (StringUtils.isBlank(dbName)) {
			return getUrl(type, hostAndPort);
		}else if (dbName.equals(JDBCConstant.MysqlSysDb.information_schema)) {
			return getInfoUrl(type, hostAndPort);
		}else if (dbName.equals(JDBCConstant.MysqlSysDb.performance_schema)) {
			return getPerfUrl(type, hostAndPort);
		}else{
			switch (type) {
				case 1:
					return "jdbc:mysql://" + hostAndPort + "/" + dbName + "?" + JDBCConstant.MYSQL_PARAM;
				case 2:
					return "jdbc:mariadb://" + hostAndPort + "/" + dbName + "?" + JDBCConstant.MYSQL_PARAM;
				case 3:
					// jdbc:oracle:thin:@localhost:1521:SID	> 连接SID服务"XE|ORCL"，进入用户的表空间
					return "jdbc:oracle:thin:@" + hostAndPort + ":" + dbName;
				default:
					throw EXP_TYPE;
			}
		}
	}

	/**
	 * 获取数据库的服务器的URL（SHOW查询）
	 *
	 * @param type			数据库类型：1 Mysql, 2 Mariadb
	 * @param host 			数据库IP
	 * @param port			数据库端口
	 * @return
	 *
	 * @author 延晓磊
	 *
	 * @since 2018年2月12日
	 */
	public static String getUrl(int type, String host, int port) {
		return getUrl(type, host +":"+ port);
	}

	/**
	 * 获取数据库的服务器的URL（SHOW查询）
	 *
	 * @param type			数据库类型：1 Mysql, 2 Mariadb
	 * @param hostAndPort	数据库地址：ip:port
	 * @return
	 *
	 * @author 延晓磊
	 *
	 * @since 2018年2月12日
	 */
	public static String getUrl(int type, String hostAndPort) {
		switch (type) {
			case 1:
				return "jdbc:mysql://" + hostAndPort;
			case 2:
				return "jdbc:mariadb://" + hostAndPort;
			default:
				throw EXP_TYPE_M;
		}
	}

	/**
	 * 获取数据库的information_schema库的URL（信息查询）
	 * 
	 * @param type			数据库类型：1 Mysql, 2 Mariadb
	 * @param hostAndPort	数据库地址：ip:port
	 * @return
	 * 
	 * @author 延晓磊
	 *
	 * @since 2018年2月12日
	 */
	public static String getInfoUrl(int type, String hostAndPort) {
		switch (type) {
		case 1:
			return "jdbc:mysql://" + hostAndPort + "/" + JDBCConstant.MysqlSysDb.information_schema;
		case 2:
			return "jdbc:mariadb://" + hostAndPort + "/" + JDBCConstant.MysqlSysDb.information_schema;
		default:
			throw EXP_TYPE_M;
		}
	}

	/**
	 * 获取数据库的performance_schema库的URL（性能查询）
	 *
	 * @param type			数据库类型：1 Mysql, 2 Mariadb
	 * @param hostAndPort	数据库地址：ip:port
	 * @return
	 *
	 * @author 延晓磊
	 *
	 * @since 2018年2月12日
	 */
	public static String getPerfUrl(int type, String hostAndPort) {
		switch (type) {
			case 1:
				return "jdbc:mysql://" + hostAndPort + "/" + JDBCConstant.MysqlSysDb.performance_schema;
			case 2:
				return "jdbc:mariadb://" + hostAndPort + "/" + JDBCConstant.MysqlSysDb.performance_schema;
			default:
				throw EXP_TYPE_M;
		}
	}
	
	/**
	 * 获取数据库连接
	 * 
	 * @param driver
	 * @param url
	 * @param user
	 * @param pwd
	 * @return
	 * 
	 * @author 延晓磊
	 *
	 * @since 2018年2月12日
	 */
	public static Connection getConnection(String driver,String url,String user,String pwd) {
		try {
			// 注册驱动
			Class.forName(driver);
			// 创建连接Connection（连接 , 帐户 , 密码）
			return DriverManager.getConnection(url, user, pwd);
		} catch (SQLException | ClassNotFoundException e) {
			throw new RuntimeException(CONN_ERROR,e);
		}
	}
	
	/**
	 * 关闭数据库连接 
	 * 
	 * @param conn
	 * 
	 * @author yxl
	 * 
	 * @since 2016-7-31
	 */
	public static void closeConnection(Connection conn) {
		if (conn != null) {
			try {
				conn.close();
			} catch (Exception e) {
				// 无操作
			}
		}
	}
	
	/**
	 * 功能描述：释放ResultSet和Statement资源
	 * 
	 * @param rs
	 * @param stm
	 * 
	 * @author yxl
	 * 
	 * @since 2016-7-31
	 */
	public static void closeRS(ResultSet rs, Statement stm) {
		if (rs != null) {
			try {
				rs.close();
			} catch (Exception e) {
				LOG.error("关闭ResultSet失败",e);
			}
		}
		if (stm != null) {
			try {
				stm.close();
			} catch (Exception e) {
				LOG.error("关闭Statement失败",e);
			}
		}
	}

	/**
	 * 将DB名称转换为java名称：xx_xx ==> xxXx
	 *
	 * @param dbName
	 * @return javaName
	 * 
	 * @author yxl
	 *
	 * @since 2016年7月9日
	 */
	public static String toJavaName(String dbName) {
		return StringUtil.parseLineToHump(dbName);
	}
	
	/**
	 * 将java名称转换为DB名称：xxXX ==> xx_xx
	 *
	 * @param 	javaName
	 * @return	db_name
	 * 
	 * @author yxl
	 *
	 * @since 2016年7月9日
	 */
	public static String toDbName(String javaName) {
		return StringUtil.parseHumpToLine(javaName);
	}
 
	/**
	 * 功能描述：根据Mysql类型获取Java类型
	 *
	 * @param mysqlType
	 * @return
	 * 
	 * @author yxl
	 *
	 * @since 2016年7月9日
	 */
	public static String getJavaType(String mysqlType) {
		return JDBCConstant.MYSQL_JAVA_TYPE.get(mysqlType);
	}

	/**
	 * 获取查询的值
	 *
	 * @param value	原始值
	 * @return 处理特殊符号 ' % _ \
	 *
	 * @author yxl
	 *
	 * @since 2016年7月9日
	 */
	public static String getSearchValue(String value){
		if (StringUtils.isBlank(value)) {
			return StringConstant.EMPTY;
		}
		return StringUtil.replaces(value, new String[]{"'","%","_","\\"}, new String[]{"\\'","\\%","\\_","\\\\\\\\"});
	}

	/**
	 * 获取LIKE查询的值（当getSearchValue无法满足时备用）
	 *
	 * @param value	原始值
	 * @return 处理特殊符号 ' \ 为 _
	 *
	 * @author yxl
	 *
	 * @since 2016年7月9日
	 */
	public static String getLikeValue(String value){
		if (StringUtils.isBlank(value)) {
			return StringConstant.EMPTY;
		}
		return StringUtil.replaces(value, new String[]{"'","\\"}, new String[]{"_","_"});
	}

	/**
	 * 功能描述：根据Mysql类型获取jdbc类型
	 *
	 * @param mysqlType
	 * @return
	 * 
	 * @author yxl
	 *
	 * @since 2016年7月9日
	 */
	public static String getJdbcType(String mysqlType) {
		return JDBCConstant.MYSQL_JDBC_TYPE.get(mysqlType);
	}

	/**
	 * 连接数据库服务器，执行操作语句
	 *
	 * @param type	数据库类型：1 Mysql, 2 Mariadb
	 * @param host
	 * @param port
	 * @param user
	 * @param password
	 * @param sql	执行语句，如"kill 123;"
	 * @return
	 *
	 * @author 延晓磊
	 * @since 2020年10月09日
	 */
	public static void execute(int type, String host, Integer port, String user, String password, String sql){
		Connection connection = null;
		Statement stm = null;
		try {
			// 注册驱动
			Class.forName(getDriver(type));
			// 数据库连接
			connection = DriverManager.getConnection(getUrl(type, host+":"+port), user, password);
			// 执行操作SQL
			stm = connection.createStatement();
			stm.execute(sql);
		} catch (SQLException | ClassNotFoundException e) {
			throw new BizRuntimeException(CONN_ERROR,e);
		} finally {
			// 释放资源
			closeConnection(connection);
			closeRS(null, stm);
		}
	}

	/**
	 * 连接数据库服务器，执行查询语句
	 *
	 * @param type	数据库类型：1 Mysql, 2 Mariadb
	 * @param host
	 * @param port
	 * @param user
	 * @param password
	 * @param sql	查询语句，如"SHOW DATABASES;"
	 * @param fn	查询回调，获取每一条执行结果
	 * @return
	 *
	 * @author 延晓磊
	 * @since 2020年10月09日
	 */
	public static void query(int type, String host, Integer port, String user, String password, String sql, ConsumerThrow<ResultSet,SQLException> fn){
		Connection connection = null;
		try {
			// 注册驱动
			Class.forName(getDriver(type));
			// 数据库连接
			connection = DriverManager.getConnection(getUrl(type, host+":"+port), user, password);
			// 执行查询SQL
			query(connection, sql, fn);
		} catch (SQLException | ClassNotFoundException e) {
			throw new BizRuntimeException(CONN_ERROR,e);
		} finally {
			// 释放资源
			closeConnection(connection);
		}
	}

	/**
	 * 连接数据库服务器，执行查询语句，获取单条结果
	 *
	 * @param type	数据库类型：1 Mysql, 2 Mariadb
	 * @param host
	 * @param port
	 * @param user
	 * @param password
	 * @param sql	查询语句，如"SHOW DATABASES;"
	 * @param fn	查询回调，获取每一条执行结果
	 * @return
	 *
	 * @author 延晓磊
	 * @since 2020年10月09日
	 */
	public static <R>R queryOne(int type, String host, Integer port, String user, String password, String sql, FunctionThrow<ResultSet,R,SQLException> fn){
		Connection connection = null;
		try {
			// 注册驱动
			Class.forName(getDriver(type));
			// 数据库连接
			connection = DriverManager.getConnection(getUrl(type, host+":"+port), user, password);
			// 执行查询SQL
			return queryOne(connection, sql, fn);
		} catch (SQLException | ClassNotFoundException e) {
			throw new BizRuntimeException(CONN_ERROR,e);
		} finally {
			// 释放资源
			closeConnection(connection);
		}
	}

	/**
	 * 连接数据库服务器，执行查询语句，获取多条结果
	 *
	 * @param type	数据库类型：1 Mysql, 2 Mariadb
	 * @param host
	 * @param port
	 * @param user
	 * @param password
	 * @param sql	查询语句，如"SHOW DATABASES;"
	 * @param fn	查询回调，获取每一条执行结果
	 * @return
	 *
	 * @author 延晓磊
	 * @since 2020年10月09日
	 */
	public static <R>List<R> queryList(int type, String host, Integer port, String user, String password, String sql, FunctionThrow<ResultSet,R,SQLException> fn){
		Connection connection = null;
		try {
			// 注册驱动
			Class.forName(getDriver(type));
			// 数据库连接
			connection = DriverManager.getConnection(getUrl(type, host+":"+port), user, password);
			// 执行查询SQL
			return queryList(connection, sql, fn);
		} catch (SQLException | ClassNotFoundException e) {
			throw new BizRuntimeException(CONN_ERROR,e);
		} finally {
			// 释放资源
			closeConnection(connection);
		}
	}

	/**
	 * 连接数据库服务器，执行批量查询语句
	 *
	 * @param type	数据库类型：1 Mysql, 2 Mariadb
	 * @param host
	 * @param port
	 * @param user
	 * @param password
	 * @param fn	查询操作，可以调用 JDBCUtil.query
	 * @return
	 *
	 * @author 延晓磊
	 * @since 2020年10月09日
	 */
	public static void queries(int type, String host, Integer port, String user, String password, ConsumerThrow<Connection,SQLException> fn){
		Connection connection = null;
		try {
			// 注册驱动
			Class.forName(getDriver(type));
			// 数据库连接
			connection = DriverManager.getConnection(getUrl(type, host+":"+port), user, password);
			// 执行批量查询
			fn.accept(connection);
		} catch (SQLException | ClassNotFoundException e) {
			throw new BizRuntimeException(CONN_ERROR,e);
		} finally {
			// 释放资源
			closeConnection(connection);
		}
	}

	/**
	 * 连接数据库，执行查询语句
	 *
	 * @param dbName 数据库
	 * @param type	数据库类型：1 Mysql, 2 Mariadb
	 * @param host
	 * @param port
	 * @param user
	 * @param password
	 * @param sql	查询语句
	 * @param fn	查询回调，获取每一条执行结果
	 * @return
	 *
	 * @author 延晓磊
	 * @since 2020年10月09日
	 */
	public static void query(String dbName, int type, String host, Integer port, String user, String password, String sql, ConsumerThrow<ResultSet,SQLException> fn){
		Connection connection = null;
		try {
			// 注册驱动
			Class.forName(getDriver(type));
			// 数据库连接
			connection = DriverManager.getConnection(getUrl(type, host+":"+port, dbName), user, password);
			// 执行查询SQL
			query(connection, sql, fn);
		} catch (SQLException | ClassNotFoundException e) {
			throw new BizRuntimeException(CONN_ERROR,e);
		} finally {
			// 释放资源
			closeConnection(connection);
		}
	}

	/**
	 * 连接数据库，执行查询语句，获取单条结果
	 *
	 * @param dbName 数据库
	 * @param type	数据库类型：1 Mysql, 2 Mariadb
	 * @param host
	 * @param port
	 * @param user
	 * @param password
	 * @param sql	查询语句
	 * @param fn	查询回调，获取每一条执行结果
	 * @return
	 *
	 * @author 延晓磊
	 * @since 2020年10月09日
	 */
	public static <R>R queryOne(String dbName, int type, String host, Integer port, String user, String password, String sql, FunctionThrow<ResultSet,R,SQLException> fn){
		Connection connection = null;
		try {
			// 注册驱动
			Class.forName(getDriver(type));
			// 数据库连接
			connection = DriverManager.getConnection(getUrl(type, host+":"+port, dbName), user, password);
			// 执行查询SQL
			return queryOne(connection, sql, fn);
		} catch (SQLException | ClassNotFoundException e) {
			throw new BizRuntimeException(CONN_ERROR,e);
		} finally {
			// 释放资源
			closeConnection(connection);
		}
	}

	/**
	 * 连接数据库，执行查询语句，获取多条结果
	 *
	 * @param dbName 数据库
	 * @param type	数据库类型：1 Mysql, 2 Mariadb
	 * @param host
	 * @param port
	 * @param user
	 * @param password
	 * @param sql	查询语句
	 * @param fn	查询回调，获取每一条执行结果
	 * @return
	 *
	 * @author 延晓磊
	 * @since 2020年10月09日
	 */
	public static <R>List<R> queryList(String dbName, int type, String host, Integer port, String user, String password, String sql, FunctionThrow<ResultSet,R,SQLException> fn){
		Connection connection = null;
		try {
			// 注册驱动
			Class.forName(getDriver(type));
			// 数据库连接
			connection = DriverManager.getConnection(getUrl(type, host+":"+port, dbName), user, password);
			// 执行查询SQL
			return queryList(connection, sql, fn);
		} catch (SQLException | ClassNotFoundException e) {
			throw new BizRuntimeException(CONN_ERROR,e);
		} finally {
			// 释放资源
			closeConnection(connection);
		}
	}

	/**
	 * 连接数据库，执行批量查询语句
	 *
	 * @param dbName 数据库
	 * @param type	数据库类型：1 Mysql, 2 Mariadb
	 * @param host
	 * @param port
	 * @param user
	 * @param password
	 * @param fn	查询操作，可以调用 JDBCUtil.query
	 * @return
	 *
	 * @author 延晓磊
	 * @since 2020年10月09日
	 */
	public static void queries(String dbName, int type, String host, Integer port, String user, String password, ConsumerThrow<Connection,SQLException> fn){
		Connection connection = null;
		try {
			// 注册驱动
			Class.forName(getDriver(type));
			// 数据库连接
			connection = DriverManager.getConnection(getUrl(type, host+":"+port, dbName), user, password);
			// 执行批量查询
			fn.accept(connection);
		} catch (SQLException | ClassNotFoundException e) {
			throw new BizRuntimeException(CONN_ERROR,e);
		} finally {
			// 释放资源
			closeConnection(connection);
		}
	}

	/**
	 * 执行查询操作
	 *
	 * @param connection
	 * @param sql
	 * @param fn	获取每一条执行结果
	 *
	 * @author 延晓磊
	 * @since 2020年10月10日
	 */
	public static void query(Connection connection, String sql, ConsumerThrow<ResultSet, SQLException> fn) throws SQLException {
		Statement stm = null;
		ResultSet rs = null;
		try {
			// 创建Statement
			stm = connection.createStatement();
			// 执行查询语句
			stm.executeQuery(sql);
			// 获取查询结果
			rs = stm.getResultSet();
			while (rs.next()) {
				fn.accept(rs);
			}
		} finally {
			// 关闭Statement
			closeRS(rs, stm);
		}
	}

	/**
	 * 执行查询操作，单条结果
	 *
	 * @param connection
	 * @param sql
	 * @param fn	获取每一条执行结果
	 *
	 * @param
	 *
	 * @author 延晓磊
	 * @since 2020年10月10日
	 */
	public static <R>R queryOne(Connection connection, String sql, FunctionThrow<ResultSet, R, SQLException> fn) throws SQLException {
		Statement stm = null;
		ResultSet rs = null;
		try {
			// 创建Statement
			stm = connection.createStatement();
			// 执行查询语句
			stm.executeQuery(sql);
			// 获取查询结果
			rs = stm.getResultSet();
			if (rs.next()) {
				return fn.apply(rs);
			}
			return null;
		} finally {
			// 关闭Statement
			closeRS(rs, stm);
		}
	}

	/**
	 * 执行查询操作，多条结果
	 *
	 * @param connection
	 * @param sql
	 * @param fn	获取每一条执行结果
	 *
	 * @param
	 *
	 * @author 延晓磊
	 * @since 2020年10月10日
	 */
	public static <R>List<R> queryList(Connection connection, String sql, FunctionThrow<ResultSet, R, SQLException> fn) throws SQLException {
		Statement stm = null;
		ResultSet rs = null;
		try {
			// 创建Statement
			stm = connection.createStatement();
			// 执行查询语句
			stm.executeQuery(sql);
			// 获取查询结果
			rs = stm.getResultSet();
			List<R> list = new ArrayList<>();
			while (rs.next()){
				list.add(fn.apply(rs));
			}
			return list;
		} finally {
			// 关闭Statement
			closeRS(rs, stm);
		}
	}

	/**
	 * 连接数据库information_schema表，执行查询语句
	 *
	 * @param type	数据库类型：1 Mysql, 2 Mariadb
	 * @param host
	 * @param port
	 * @param user
	 * @param password
	 * @param sql	查询语句
	 * @param fn	获取每一条执行结果
	 * @return
	 *
	 * @author 延晓磊
	 * @since 2020年10月09日
	 */
	public static <R>List<R> queryInfo(int type, String host, Integer port, String user, String password, String sql, FunctionThrow<ResultSet,R,SQLException> fn){
		Connection connection = null;
		try {
			// 注册驱动
			Class.forName(getDriver(type));
			// 数据库连接
			connection = DriverManager.getConnection(getInfoUrl(type, host+":"+port), user, password);
			// 执行查询SQL
			return queryList(connection, sql, fn);
		} catch (SQLException | ClassNotFoundException e) {
			throw new BizRuntimeException(CONN_ERROR,e);
		} finally {
			// 释放资源
			closeConnection(connection);
		}
	}

	/**
	 * 连接数据库information_schema表，执行批量查询语句
	 *
	 * @param type	数据库类型：1 Mysql, 2 Mariadb
	 * @param host
	 * @param port
	 * @param user
	 * @param password
	 * @param fn	查询操作，可以调用 JDBCUtil.query
	 * @return
	 *
	 * @author 延晓磊
	 * @since 2020年10月09日
	 */
	public static void queryInfos(int type, String host, Integer port, String user, String password, ConsumerThrow<Connection,SQLException> fn){
		Connection connection = null;
		try {
			// 注册驱动
			Class.forName(getDriver(type));
			// 数据库连接
			connection = DriverManager.getConnection(getInfoUrl(type, host+":"+port), user, password);
			// 执行批量查询
			fn.accept(connection);
		} catch (SQLException | ClassNotFoundException e) {
			throw new BizRuntimeException(CONN_ERROR,e);
		} finally {
			// 释放资源
			closeConnection(connection);
		}
	}

	/**
	 * 根据条件查询记录数
	 *
	 * @param conn
	 * @param tableName
	 * @param condition 查询条件(WHERE 之后的代码)
	 * @return
	 * @throws SQLException
	 *
	 * @author 延晓磊
	 * @since 2020年10月09日
	 */
	public static Long count(Connection conn, String tableName,String condition) throws SQLException{
		return JDBCUtil.queryOne(conn, "SELECT COUNT(*) FROM " + tableName +" WHERE "+condition, rs -> rs.getLong(1));
	}

}
